/**
 * @file toperrors.c
 * @brief toperrors functions.
 * @date Dec-2011
 * @author Leonardo Lino 2101101
 * @author Joaquim Ferreira 2101104
 * @version 1.0
 */
 
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <unistd.h>

#include "debug.h"
#include "files.h"
#include "memory.h"
#include "crypt.h"
#include "listas.h"
#include "hashtables.h"
#include "str_utils.h"
#include "spell.h"
#include "toperrors.h"
#include "pthrds.h"

/** @brief SPELL_OPTION 3 - A constant to use for define a type. */
#define SPELL_OPTION 3
/** @brief TOPERR_OPTION 4 - A constant to use for define a type. */
#define TOPERR_OPTION 4

#define ERR_IO 1

/** @brief Hashtable which holds the final results. All hashtables are merged into this one. */
HASHTABLE_T* finalResults;


/** @brief Main processor for top-errors functionality */
void mainTopErrors(char *foldername, char *dictionary, int numThreads){
	
	#ifdef SHOW_DEBUG
    DEBUG ("my debug \n\n");
    #endif
    
    /** @brief Initiate hashtable */
    finalResults = tabela_criar(101, (LIBERTAR_FUNC)free_tabela);
    
    /** @brief top-errors functionality is integrated into freq-folder functionality. As such uses it's code for threads */
    m_thread (foldername, numThreads, TOPERR_OPTION, dictionary);
}


/** @brief Used to process each individual file */
HASHTABLE_T* processEachWordTopError(LISTA_GENERICA_T* fileHolder,HASHTABLE_T* dictHolder){
	ITERADOR_T* it;
	LISTA_GENERICA_T* resultsList;
	char *word;
	int found;
	HASHTABLE_T* localHash;
	
	
	localHash = tabela_criar(101, (LIBERTAR_FUNC)free_tabela);
	
	it = lista_criar_iterador(fileHolder);
	
	while ((word = (char*)iterador_proximo_elemento(it)) != NULL){
		resultsList = lista_criar(free);
		
		found = checkDict(dictHolder, resultsList, word, finalResults);
		if (found==0){
			checkHash(localHash,resultsList, word);
		}

	}
	iterador_destruir(&it);
	
	
	return localHash;
}

/** @brief Check if a specified word is inside a hashtable */
void checkHash(HASHTABLE_T* currentHash, LISTA_GENERICA_T* resultsList, char *word){
	STRTOPERR *st=NULL;
	
	LISTA_GENERICA_T* localList;
	
	st = (STRTOPERR *) tabela_consultar(currentHash, word);
	
	
	if (st==NULL){
		st = (STRTOPERR *) MY_MALLOC (sizeof (STRTOPERR));
		st->count=1;
		st->sinonims=resultsList;
		tabela_inserir(currentHash, strdup(word), st);
	} else {
		st->count++;
		localList = (LISTA_GENERICA_T*) st->sinonims;
		insertSinonims(localList,resultsList);
	}
}

/** @brief insert words from one list into another */
void insertSinonims(LISTA_GENERICA_T* origin, LISTA_GENERICA_T* destination){
	ITERADOR_T* it;
	char *word;
	char *res;
	
	it = lista_criar_iterador(origin);
	while ((word = (char*)iterador_proximo_elemento(it)) != NULL){
		
		res = lista_pesquisar(destination, word, (COMPARAR_FUNC)compareForLists);
		if (res==NULL)
			lista_inserir_ordenado(destination,strdup(word),(COMPARAR_FUNC)compareSinonims);
	}
	iterador_destruir(&it);
	
}

/**< @brief Routine used to free memory used by finalResults hashtable */
void freeFinalResults(HASHTABLE_T* finalResults){
	
	LISTA_GENERICA_T* lista_aux;
	ITERADOR_T* it;
	char *item=NULL;
	STRTOPERR *st=NULL;
	LISTA_GENERICA_T* lsT;
	
	
	lista_aux = tabela_criar_lista_chaves(finalResults);
	it = lista_criar_iterador(lista_aux);
	while ((item = (char*)iterador_proximo_elemento(it)) != NULL){
		
		st=(STRTOPERR *) tabela_consultar(finalResults,item);
		lsT= (LISTA_GENERICA_T*) st->sinonims;
		
		lista_destruir(&lsT);

	}
	iterador_destruir(&it);
	lista_destruir(&lista_aux);
	tabela_destruir(&finalResults);
}



int compareForLists(char* a1, char* a2) {
	return strcmp(a1, a2);
}

/** @brief Join 2 hashtables */
void mergeHashes(HASHTABLE_T* origin, HASHTABLE_T* destination){
	LISTA_GENERICA_T* lista_aux;
	ITERADOR_T* it;
	char *item;
	STRTOPERR *st=NULL;
	
	lista_aux = tabela_criar_lista_chaves(origin);
	it = lista_criar_iterador(lista_aux);
	while ((item = (char*)iterador_proximo_elemento(it)) != NULL){
		st=(STRTOPERR *) tabela_consultar(origin,item);
		checkHashFinal(st,item, destination);
	}
	iterador_destruir(&it);
	lista_destruir(&lista_aux);
}


/** @brief Similar to checkHash() but also needs to handle a structure */
void checkHashFinal(STRTOPERR *jn, char* item, HASHTABLE_T* destination){
	STRTOPERR *st;
	LISTA_GENERICA_T* resultsList;
	
	st = (STRTOPERR *) tabela_consultar(destination, item);
	
	if (st==NULL){
		st = (STRTOPERR *) MY_MALLOC (sizeof (STRTOPERR));
		st->count = jn->count;
		st->sinonims=(LISTA_GENERICA_T*) jn->sinonims;
		tabela_inserir(finalResults, strdup(item), st);
	} else {
		st->count += jn->count;
		resultsList = (LISTA_GENERICA_T*) st->sinonims;
		insertSinonims((LISTA_GENERICA_T*)jn->sinonims,resultsList);
	}
	
}

/** @brief Print to screen our final hash */
void outputTopHash(HASHTABLE_T* currentHash){
	LISTA_GENERICA_T* lista_aux;
	ITERADOR_T* it;
	ITERADOR_T* it2;
	char *item;
	char *item2;
	STRTOPERR *st=NULL;
	LISTA_GENERICA_T* lsT;
	int i=0;
	
	lista_aux = tabela_criar_lista_chaves(currentHash);
	it = lista_criar_iterador(lista_aux);
	while ((item = (char*)iterador_proximo_elemento(it)) != NULL){
		i=0;
		printf("%s:",item);
		st=(STRTOPERR *) tabela_consultar(currentHash,item);
		lsT= (LISTA_GENERICA_T*) st->sinonims;
		printf("%d: ", st->count);
		
		it2 = lista_criar_iterador(lsT);
		while ((item2 = (char*)iterador_proximo_elemento(it2)) != NULL){
			if (i !=0 ) printf(",");
			printf("%s", item2);
			i++;
		}
		if (i==0) printf("?");
		printf("\n");
		iterador_destruir(&it2);

	}
	iterador_destruir(&it);
	lista_destruir(&lista_aux);
	
}

/** @brief Deprecated function maintained for debugging */
LISTA_GENERICA_T* folderProcessor(char *foldername){
	 LISTA_GENERICA_T *foldersList = NULL;
	 char *currentFolder;
	 char *full_path;
	 LISTA_GENERICA_T *txtList = NULL;
	 DIR *dir = NULL;
	 struct dirent *result = NULL;
	 struct stat stats;
	 struct dirent entry;
	 int return_code;
	 
	 foldersList = lista_criar (free);
	 txtList = lista_criar (free);
	 
	 lista_inserir (foldersList, foldername);
	 
	 while (lista_numero_elementos (foldersList) != 0) { 
		 
		 currentFolder = lista_remover_inicio (foldersList); 
	 
		 if((dir = opendir(currentFolder)) == NULL)
				ERROR(ERR_IO,"Opendir error!");
		
		 for (return_code = readdir_r(dir, &entry, &result);
			 result != NULL && return_code == 0;
			 return_code = readdir_r(dir, &entry, &result)){
		  
		  if(strcmp(entry.d_name,".") == 0 || strcmp(entry.d_name,"..") == 0)
				continue;
		  
		  full_path = path_cat(currentFolder,entry.d_name);
		  
		  if (lstat(full_path,&stats) != 0) 
			ERROR(ERR_IO,"Stat error on file %s!", full_path);
			
		  if(S_ISDIR(stats.st_mode)) {
				lista_inserir_fim(foldersList, full_path);
		  }
			
		  if(S_ISREG(stats.st_mode)) {
			  if (check_file_type (full_path, ".txt")) {
					lista_inserir(txtList,full_path);
					G_proc_files++;
				} else {
					MY_FREE (full_path);
				}
		  }
 
		}

		closedir(dir);
	}
	
	lista_destruir(&foldersList);

return txtList;	
}
